﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

namespace FtpHelper
{
    public sealed class Ftp
    {
        private readonly string _host;
        private readonly string _password;
        private readonly string _userName;

        public Ftp(string host, string userName, string password)
        {
            _host = host;
            _userName = userName;
            _password = password;
        }

        public string Host
        {
            get { return _host; }
        }

        public string UserName
        {
            get { return _userName; }
        }

        public string Password
        {
            get { return _password; }
        }

        public async Task<bool> DirectoryExists(string rootDir, string dir, bool keepAlive)
        {
            var target = new Uri(rootDir);
            var reqFtp = (FtpWebRequest)WebRequest.Create((target));
            reqFtp.Credentials = new NetworkCredential(_userName, _password);
            reqFtp.KeepAlive = keepAlive;
            reqFtp.Method = WebRequestMethods.Ftp.ListDirectory;

            try
            {
                var response = await reqFtp.GetResponseAsync();
                var responseStream = response.GetResponseStream();

                if (responseStream == null)
                    return false;

                var stream = new StreamReader(responseStream, System.Text.Encoding.ASCII);
                var ftpDirString = stream.ReadLine();
                var ftpDirs = new List<string>();
                while (ftpDirString != null)
                {
                    ftpDirs.Add(ftpDirString);
                    ftpDirString = stream.ReadLine();
                }

                return ftpDirs.Contains(dir) ? true : false;

            }
            catch (WebException)
            {
                return false;
            }
        }

        public async Task MakeDirectory(string dirName, bool keepAlive)
        {
            var target = new Uri(dirName);
            var reqFtp = (FtpWebRequest)WebRequest.Create((target));
            reqFtp.Credentials = new NetworkCredential(_userName, _password);
            reqFtp.KeepAlive = keepAlive;
            reqFtp.Method = WebRequestMethods.Ftp.MakeDirectory;
            await reqFtp.GetResponseAsync();
        }

        public async Task DeleteFile(string url, bool keepAlive)
        {
            var target = new Uri(url);
            var reqFtp = (FtpWebRequest)WebRequest.Create((target));
            reqFtp.Credentials = new NetworkCredential(_userName, _password);
            reqFtp.KeepAlive = keepAlive;
            reqFtp.Method = WebRequestMethods.Ftp.DeleteFile;
            await reqFtp.GetResponseAsync();
        }

        public async Task<long> GetFileSize(string url, bool keepAlive)
        {
            long size = 0;
            var target = new Uri(url);
            var reqFtp = (FtpWebRequest)WebRequest.Create((target));
            reqFtp.Credentials = new NetworkCredential(_userName, _password);
            reqFtp.KeepAlive = keepAlive;
            reqFtp.Method = WebRequestMethods.Ftp.GetFileSize;
            var response = await reqFtp.GetResponseAsync();
            size = response.ContentLength;
            return size;
        }

        public async Task RenameTo(string fileName, string newFileName, bool keepAlive)
        {
            var target = new Uri(fileName);
            var reqFtp = (FtpWebRequest)WebRequest.Create((target));
            reqFtp.Method = WebRequestMethods.Ftp.Rename;
            reqFtp.Credentials = new NetworkCredential(_userName, _password);
            reqFtp.KeepAlive = keepAlive;
            reqFtp.RenameTo = newFileName;
            var response = await reqFtp.GetResponseAsync();
        }

        public async Task<int> Upload(FileInfo localPath, string ftpPath, bool useBinary, bool appendToFile, int bufferLength, bool keepAlive, bool passive,
            CancellationToken cancellationToken = new CancellationToken(), IProgress<uint> progress = null)
        {
            var count = 0; // for uploaded byte.
            var target = new Uri(ftpPath);

            var reqFtp = (FtpWebRequest)WebRequest.Create((target));
            reqFtp.Credentials = new NetworkCredential(_userName, _password);
            reqFtp.UsePassive = passive;
            reqFtp.KeepAlive = keepAlive;
            reqFtp.UseBinary = useBinary;//true for image and false for text

            long offset = 0;
            if (appendToFile)
            {
                reqFtp.Method = WebRequestMethods.Ftp.AppendFile;
                try
                {
                    offset = await GetFileSize(target.ToString(), true);
                }
                catch
                {
                    //keep offset =0 if can not get file size from ftp
                }
                if (localPath.Length < offset)
                {
                    //defferent file size, need overwriting
                    offset = 0;
                }
                else
                {
                    if (localPath.Length == offset)
                    {
                        //skip completed file
                        return 0;
                    }
                }
            }
            else
            {
                reqFtp.Method = WebRequestMethods.Ftp.UploadFile;
            }

            var buffer = new byte[bufferLength];
            using (var fs = localPath.OpenRead())
            {
                if (offset != 0)
                    fs.Seek(offset, SeekOrigin.Begin);

                using (var requestStream = await reqFtp.GetRequestStreamAsync())
                {
                    var readBytes = await fs.ReadAsync(buffer, 0, bufferLength);

                    while (readBytes != 0)
                    {
                        // Write Content from the file stream to the FTP Upload Stream
                        count += readBytes;
                        await requestStream.WriteAsync(buffer, 0, readBytes);
                        readBytes = await fs.ReadAsync(buffer, 0, bufferLength);

                        cancellationToken.ThrowIfCancellationRequested();

                        if (progress != null)
                            progress.Report((uint)readBytes);
                    }
                }
            }

            return count;
        }

    }
}
